#include "Utility/PropertyParser.hpp"
#include "Common.hpp"
#include "Utility/Parsables/IParsable.hpp"
#include "Utility/Singleton.hpp"

#include <list>
#include <stack>
#include <string>

namespace Utility
{
	PropertyParser::PropertyParser( const std::string path )
		: commentOpen( false ), quoteOpen( false ), file( path.c_str() )
	{
		PreProcess();
		Parse();
		Parsables::IParsable::DestroyAll();
	}

	PropertyParser::~PropertyParser( void )
	{
	}

	int PropertyParser::Tokenise( const char c )
	{
		if ( c == '\"' || c == '\'' )
		{
			quoteOpen = !quoteOpen;

			if ( quoteOpen )
			{
				return TOKEN_STRING_OPEN;
			}
			else
			{
				return TOKEN_STRING_CLOSE;
			}
		}
		else if ( c == '{' )
		{
			return TOKEN_PROPERTY_FIELD_BEGIN;
		}
		else if ( c == '}' )
		{
			return TOKEN_PROPERTY_FIELD_CLOSE;
		}
		else if ( c == '=' )
		{
			return TOKEN_PROPERTY_ASSIGN;
		}
		else if ( c == ';' )
		{
			return TOKEN_PROPERTY_NEXT;
		}
		else
		{
			return TOKEN_STRING_VALUE;
		}
	}

	void PropertyParser::PreProcess( void )
	{
		std::string str;
		bool lastWasStrVal = false;

		while ( true )
		{
			char ch;
			file.get( ch );

			if ( file.eof() )
			{
				break;
			}

			if ( ch == '/' && file.peek() == '/' )
			{
				commentOpen = true;
			}

			if ( ch == '\n' && commentOpen )
			{
				commentOpen = false;
			}

			if ( !commentOpen && ch != ' ' && ch != '\n' && ch != '\t' && ch != '\r' )
			{
				int tok = Tokenise( ch );

				if ( tok == TOKEN_STRING_VALUE )
				{
					str += ch;
					lastWasStrVal = true;
				}
				else
				{
					FieldVal v;

					if ( lastWasStrVal )
					{
						v.Type = TOKEN_STRING_VALUE;
						v.Value = str;

						preprocessed.push_back( v );
						lastWasStrVal = false;
					}

					str = ch;

					v.Type  = tok;
					v.Value = str;

					preprocessed.push_back( v );
					str = "";
				}
			}
		} 
	}

	void PropertyParser::Parse( void )
	{
		std::string str;
		std::stack< std::string > keys;
		std::stack< Parsables::Property > vals;
		std::stack< bool > expectVals;

		std::stack< Parsables::IParsable * > objects;
		Parsables::IParsable *arg = nullptr;

		for ( std::list< FieldVal >::iterator itr = preprocessed.begin(); itr != preprocessed.end(); itr++ )
		{
			FieldVal field = *itr;

			if ( field.Type == TOKEN_STRING_OPEN )
			{
				itr++;
				field = *itr;

				if ( field.Type != TOKEN_STRING_VALUE )
				{
					//Report error: expected string

					return;
				}

				str = field.Value;
			}
			else if ( field.Type == TOKEN_STRING_CLOSE )
			{
				if ( expectVals.top() )
				{
					Parsables::Property val; 
					val.type = Parsables::PROPERTY_TYPE_STRING;
					val.str = str;

					vals.push( val );
					expectVals.top() = false;
				}
				else
				{
					keys.push( str );
				}
			}
			else if ( field.Type == TOKEN_PROPERTY_ASSIGN )
			{
				expectVals.top() = true;
			}
			else if ( field.Type == TOKEN_PROPERTY_NEXT )
			{
				if ( objects.size() > 0 )
				{
					if ( arg )
					{
						Parsables::Property val; 
						val.type = Parsables::PROPERTY_TYPE_OBJECT;
						val.obj = arg;

						objects.top()->SetProperty( keys.top(), val );

						expectVals.top() = false;
						keys.pop();

						arg = nullptr;
					}
					else
					{
						objects.top()->SetProperty( keys.top(), vals.top() );
						keys.pop();
						vals.pop();
					}
				}
				else
				{
					//Report error: invalid object
				}
			}
			else if ( field.Type == TOKEN_PROPERTY_FIELD_CLOSE )
			{
				objects.top()->Create();
				expectVals.pop();

				if ( expectVals.size() > 0 && expectVals.top() )
				{
					arg = objects.top();
				}
				else
				{
					arg = nullptr;
				}

				objects.pop();
			}
			else if ( field.Type == TOKEN_STRING_VALUE )
			{
				objects.push( Singleton< ParsablesFactory >::Get()->Create( field.Value ) );
				expectVals.push( false );
			}
		}
	}
}